package com.samuel.basics.controller.es;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.CollectionUtils;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.FuzzyQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.ScoreSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.samuel.basics.entity.BookEntity;
import com.samuel.basics.enums.ResultEnum;
import com.samuel.basics.model.ResultVo;
import com.samuel.basics.model.ResultVoUtil;
import com.samuel.basics.utils.JsonUtil;

/**
 * <p></p>
 *
 * @author aneng huang
 * @since 2020-12-25 09:26:52
 */
@Controller
@RequestMapping("books")
public class BookController {

    private static final Logger logger = LoggerFactory.getLogger(BookController.class);

    @Resource
    private RestHighLevelClient restHighLevelClient;

    @Value("${elasticsearch.index}")
    private String index;

    /**
     * 创建索引
     * 
     * @param bookEntity	书籍对象
     * @return		创建索引是否成功
     */
    @ResponseBody
    @PostMapping("createIndex")
    public ResultVo createIndex(BookEntity bookEntity) {
        // 创建🔍搜索请求
        IndexRequest indexRequest = new IndexRequest(index);
        try {
            String s = JsonUtil.toJson(bookEntity).orElseThrow(NullPointerException::new);
            // 写入内容
            indexRequest.source(s, XContentType.JSON);
            // 创建索引
            restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        } catch (IOException | NullPointerException e) {
            e.printStackTrace();
            return ResultVoUtil.error(ResultEnum.ADD_ERROR);
        }
        return ResultVoUtil.success();
    }

    @ResponseBody
    @GetMapping("query")
    public Object query(BookEntity bookEntity) throws IOException {
        // 创建查询工厂对象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        // 创建查询对象
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

        // 创建查询条件对象
        // 1. 完全搜索模式
        MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("bookName", bookEntity.getBookName());
        boolQueryBuilder.must(matchQueryBuilder);
        // 2. 模糊搜索模式
        FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("authorName", bookEntity.getAuthorName());
        boolQueryBuilder.must(fuzzyQueryBuilder);
        fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("context", bookEntity.getAuthorName());
        boolQueryBuilder.must(fuzzyQueryBuilder);
        // 3. 多字段查询
        // MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(bookEntity.getBookName(), "bookName", "context", "authorName");
        // boolQueryBuilder.must(multiMatchQueryBuilder);
        // 4. 区域模式
        // RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("createDate");
        // rangeQueryBuilder.gte("2020-12-15T08:00:00");
        // rangeQueryBuilder.lte("2020-12-28T23:00:00");
        // boolQueryBuilder.must(rangeQueryBuilder);
        // 将条件设置到查询工厂中
        searchSourceBuilder.query(boolQueryBuilder);

        // 创建查询请求对象: 从某个索引片段创建请求对象
        SearchRequest searchRequest = new SearchRequest(index);
        searchRequest.source(searchSourceBuilder);
        logger.info("请求参数: {}", searchRequest.source().toString());
        // 执行请求
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        //获取source
        List<Map<String, Object>> collect = Arrays.stream(searchResponse.getHits().getHits())
                .map(SearchHit::getSourceAsMap).collect(Collectors.toList());
        logger.info("返回结果: {}", collect);
        return collect;
    }


    @ResponseBody
    @GetMapping("getByIndex")
    public ResultVo getByIndex(BookEntity bookEntity) {
        int from = 0;
        int size = 10;
        Map<String, Object> where = new HashMap<>();
        where.put("bookName", bookEntity.getBookName());
        where.put("authorName", bookEntity.getAuthorName());
        Map<String, Boolean> sortFieldsToAsc = new HashMap<>();
        // sortFieldsToAsc.put("bookName", true);
        String[] includeFields = new String[] {};
        String[] excludeFields = new String[] {};
        int timeOut = 2000;
        List<Map<String, Object>> maps = searchIndex(index, from, size, where, sortFieldsToAsc,
                includeFields, excludeFields, timeOut);

        if (maps.isEmpty()) {
            return ResultVoUtil.error(ResultEnum.ADD_ERROR);
        }

        return ResultVoUtil.success(maps);
    }

    /**
     * 搜索🔍内容
     * @param index             索引
     * @param from              分页开始页
     * @param size              每页大小
     * @param where             条件
     * @param sortFieldsToAsc   排序
     * @param includeFields     包含的字段
     * @param excludeFields     排除的字段
     * @param timeOut           超时连接时间设置
     * @return                  内容
     */
    @SuppressWarnings("unchecked")
    public List<Map<String, Object>> searchIndex(String index, int from, int size, Map<String, Object> where,
                                                 Map<String, Boolean> sortFieldsToAsc, String[] includeFields,
                                                 String[] excludeFields, int timeOut) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
        try {
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            //条件
            if (where != null && !where.isEmpty()) {
                BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
                for (Map.Entry<String, Object> entry : where.entrySet()) {
                    String k = entry.getKey();
                    Object v = entry.getValue();
                    if (v instanceof Map) {
                        //范围选择map  暂定时间
						Map<String, Date> mapV = (Map<String, Date>) v;
                        boolQueryBuilder.must(
                                // 范围查询
                                QueryBuilders.rangeQuery(k)
                                        .gte(format.format(mapV.get("start")))
                                        .lt(format.format(mapV.get("end"))));
                    } else {
                        //普通模糊匹配
                        boolQueryBuilder.must(QueryBuilders.fuzzyQuery(k, v.toString()));
                    }
                }
                sourceBuilder.query(boolQueryBuilder);
            }

            //分页
            from = from <= -1 ? 0 : from;
            // size = size >= 1000 ? 1000 : size;
            size = Math.min(size, 1000);
            size = size <= 0 ? 15 : size;
            sourceBuilder.from(from);
            sourceBuilder.size(size);

            //超时
            sourceBuilder.timeout(new TimeValue(timeOut, TimeUnit.SECONDS));

            //排序
            if (sortFieldsToAsc != null && !sortFieldsToAsc.isEmpty()) {
                sortFieldsToAsc.forEach((k, v) -> sourceBuilder.sort(new FieldSortBuilder(k).order(v ? SortOrder.ASC : SortOrder.DESC)));
            } else {
                sourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));
            }

            //返回和排除列
            if (!CollectionUtils.isEmpty(includeFields) || !CollectionUtils.isEmpty(excludeFields)) {
                sourceBuilder.fetchSource(includeFields, excludeFields);
            }

            SearchRequest searchRequest = new SearchRequest(index);
            //各种组合条件
            searchRequest.source(sourceBuilder);

            //请求
            logger.info("请求参数: {}", searchRequest.source().toString());
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

            //解析返回
            if (searchResponse.status() != RestStatus.OK || searchResponse.getHits().getTotalHits().value <= 0) {
                logger.info("返回结果为空！");
                return Collections.emptyList();
            }

            //获取source
            List<Map<String, Object>> collect = Arrays.stream(searchResponse.getHits().getHits())
                    .map(SearchHit::getSourceAsMap).collect(Collectors.toList());
            logger.info("返回结果: {}", collect);
            return collect;

        } catch (Exception ex) {
            ex.printStackTrace();
        }
        logger.info("返回结果为空！");
        return Collections.emptyList();
    }

    @ResponseBody
    @PostMapping("update")
    public ResultVo update(BookEntity bookEntity) throws IOException {
        UpdateRequest updateRequest = new UpdateRequest(index, "d7MOmXYBiVQIyLFgo7QM");
        /*
        String s = JsonUtil.toJson(bookEntity).get();
        updateRequest.doc(s, XContentType.JSON);
        **/
        Map<String, Object> map = new HashMap<>();
        map.put("bookName", bookEntity.getBookName());
        map.put("authorName", bookEntity.getAuthorName());
        map.put("context", bookEntity.getContext());
        updateRequest.doc(map, XContentType.JSON);

        restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        return ResultVoUtil.success();
    }

    @ResponseBody
    @DeleteMapping("delete")
    public ResultVo delete(BookEntity bookEntity) throws IOException {
    	// 删除该索引下所有的数据
        // DeleteRequest deleteRequest = new DeleteRequest(index);
        // DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);

        // 根据条件查询数据
        DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest(index);
        deleteByQueryRequest.setQuery(QueryBuilders.fuzzyQuery("authorName", bookEntity.getAuthorName()));
        deleteByQueryRequest.setQuery(QueryBuilders.fuzzyQuery("bookName", bookEntity.getBookName()));

        BulkByScrollResponse bulkByScrollResponse = restHighLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);
        logger.info("删除成功！");
        return ResultVoUtil.success(bulkByScrollResponse);
    }
}
